﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.ComponentModel;
using PSE.Framework.Serialization;
using System.Threading;

namespace PSE.ProcessManager.ResourceAccess.FileSystemProvider
{
    public abstract class BaseFileAccess : IDisposable
    {
        #region Constants
        protected const int DEFAULT_MAX_ATTEMPTS = 20;
        protected const int DEFAULT_ATTEMPT_WAIT = 250;//ms
        #endregion

        #region Xml File Methods
        private Dictionary<string, XmlSerializer> _xmlSerializers;
        private object _syncRoot = new object();
        /// <summary>
        /// Serializar um objeto em um Xml e armazenar no arquivo indicado
        /// </summary>
        /// <param name="filePath">Caminho do arquivo</param>
        /// <param name="obj">Objeto a ser serializado e armazenado</param>
        protected void SaveToXmlFile(string filePath, object obj)
        {
            //tentar abrir um arquivo para escrita
            using (FileStream file = TyrOpenFile(filePath, DEFAULT_MAX_ATTEMPTS, DEFAULT_ATTEMPT_WAIT, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                SaveToXmlFile(file, obj);
            }
        }

        /// <summary>
        /// Serializar um objeto em um Xml e armazenar no arquivo indicado
        /// </summary>
        /// <param name="file">FileStream do arquivo</param>
        /// <param name="obj">Objeto a ser serializado e armazenado</param>
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        protected void SaveToXmlFile(FileStream file, object obj)
        {
            XmlSerializer ser = GetXmlSerializer(obj.GetType());
            ser.SerializeToFile(file, obj);
            file.Close();
        }

        /// <summary>
        /// Carregar um xml de um arquivo de deserializar em um objeto
        /// </summary>
        /// <typeparam name="T">Tipo do objeto destino</typeparam>
        /// <param name="filePath">Caminho do arquivo xml de origem</param>
        /// <returns>Objeto contendo as informações do arquivo indicado como origem</returns>
        protected T LoadFromXmlFile<T>(string filePath) where T : class
        {
            //se o arquivo não existir, retornar null
            if (!File.Exists(filePath))
                throw new FileNotFoundException(filePath);
            else
            {
                using (FileStream file = TyrOpenFile(filePath, DEFAULT_MAX_ATTEMPTS, DEFAULT_ATTEMPT_WAIT, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    return LoadFromXmlFile<T>(file);
                }
            }
        }

        /// <summary>
        /// Carregar um xml de um arquivo de deserializar em um objeto
        /// </summary>
        /// <typeparam name="T">Tipo do objeto destino</typeparam>
        /// <param name="filePath">Caminho do arquivo xml de origem</param>
        /// <returns>Objeto contendo as informações do arquivo indicado como origem</returns>
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        protected T LoadFromXmlFile<T>(FileStream file) where T : class
        {
            XmlSerializer ser = GetXmlSerializer(typeof(T));
            T ret = ser.DeserializeFromFile<T>(file);
            file.Close();
            return ret;
        }

        /// <summary>
        /// Recuperar o XmlSerializer associado a um tipo do dicionário de XmlSerializers
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private XmlSerializer GetXmlSerializer(Type type)
        {
            lock (_syncRoot)
            {
                XmlSerializer ser = null;
                string typeName = type.FullName;

                //se já existe um serializer para o tipo do objeto
                if (_xmlSerializers != null && _xmlSerializers.ContainsKey(typeName))
                    ser = _xmlSerializers[typeName];
                else
                {
                    if (_xmlSerializers == null)
                        _xmlSerializers = new Dictionary<string, XmlSerializer>();

                    ser = new XmlSerializer(type);
                    _xmlSerializers.Add(typeName, ser);
                }
                return ser;
            }
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            _xmlSerializers = null;
        }

        #endregion


        /// <summary>
        /// Tentar abrir um arquivo de forma exclusiva, tentando 
        /// </summary>
        /// <param name="filePath">caminho do arquivo</param>
        /// <param name="maxAttempts">numero máximo de tentativas de abertura do arquivo</param>
        /// <param name="timeToWait">tempo de espera entre uma tentativa e outra</param>
        /// <returns></returns>
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        protected FileStream TyrOpenFile(string filePath, int maxAttempts, int timeToWait, FileMode fileMode, FileAccess fileAccess, FileShare fileShare)
        {
            FileStream file = null;
            for (int i = 0; i < maxAttempts; i++)
            {
                try
                {
                    file = new FileStream(filePath, fileMode, fileAccess, fileShare);
                    break;
                }
                catch
                {
                    Thread.Sleep(timeToWait);
                    continue;
                }
            }

            if (file == null)
                throw new IOException(string.Format("The file {0} is already openned.", filePath));
            else
                return file;
        }
    }
}
